package com.ztesoft.book.common.utils.export.word;


import com.ztesoft.book.common.utils.base.SnowFlakeGenerator;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.IBodyElement;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.apache.xmlbeans.XmlCursor;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * @author zl
 * @ dateTime 2022/7/29 3:30 下午
 * @ description word 文档导出
 */

@Slf4j
public class WordUtils {

    /**
     * 默认字体的大小
     */
    final int DEFAULT_FONT_SIZE = 10;

    /**
     * 流 读取大小
     */
    private static final int ONE_ZERO_TWO_FOUR = 1024;
    /**
     * 当前元素的位置
     */
    int n = 0;
    /**
     * 判断当前是否是遍历的表格
     */
    boolean isTable = false;
    /**
     * 模板对象
     */
    XWPFDocument templateDoc;
    /**
     * 被list替换的段落 被替换的都是oldParagraph
     */
    private XWPFParagraph oldParagraph;
    /**
     * 参数
     */
    private Map<String, Object> paramMap;
    /**
     * 重复模式的占位符所在的行索引
     */
    private int currentRowIndex;

    /**
     * 入口
     *
     * @param paramMap     模板中使用的参数
     * @param templatePath 模板全路径
     */
    public static void process(
            Map<String, Object> paramMap, String templatePath, HttpServletResponse rep, String showName) {
        try {
            // 生成的文件存放的本地全路径
            String outPath = "/Users/zhanglei/Desktop/tem/";
            WordUtils wordUtils = new WordUtils();
            wordUtils.setParamMap(paramMap);
            wordUtils.createWord(rep, templatePath, outPath, showName);
        }
        catch (IOException e) {
            log.error("word导出报错:{}", e.getMessage());
        }
    }
    
    /**
     * 入口
     *
     * @param paramMap     模板中使用的参数
     * @param templatePath 模板全路径
     */
    public static void processNew(
            Map<String, Object> paramMap, String templatePath, HttpServletResponse rep, String showName) {
        // 生成的文件存放的本地全路径
        String outPath = "/Users/zhanglei/Desktop/tem/";
        WordUtils wordUtils = new WordUtils();
        wordUtils.setParamMap(paramMap);
        //            wordUtils.createWord(rep, templatePath, outPath, showName);
        wordUtils.createWord(templatePath, outPath);
    }
    

    /**
     * 生成动态的word
     * @param templatePath
     * @param outPath
     */
    public void createWord(String templatePath, String outPath) {
        File inFile = new File(templatePath);
        try (FileOutputStream outStream = new FileOutputStream(outPath)) {
            templateDoc = new XWPFDocument(OPCPackage.open(inFile));
            parseTemplateWord();
            templateDoc.write(outStream);
        } catch (Exception e) {
            StackTraceElement[] stackTrace = e.getStackTrace();

            String className = stackTrace[0].getClassName();
            String methodName = stackTrace[0].getMethodName();
            int lineNumber = stackTrace[0].getLineNumber();

            log.error("错误：第:{}行, 类名:{}, 方法名:{}", lineNumber, className, methodName);
            throw new RuntimeException(e.getCause().getMessage());
        }
    }

    /**
     * 生成动态的word
     *
     * @param rep          HttpServletResponse
     * @param templatePath 模板路径
     * @param outPath      输出路径
     * @param showName     文件名称
     * @throws java.io.IOException
     */
    public void createWord(
            final HttpServletResponse rep, String templatePath, String outPath,
            final String showName) throws IOException {
        URL url = new URL(templatePath);
        HttpURLConnection urlCon = (HttpURLConnection) url.openConnection();
        urlCon.setConnectTimeout(5000);
        urlCon.setReadTimeout(5000);
        InputStream fis = urlCon.getInputStream();

        BufferedInputStream br = null;
        OutputStream outputStream = null;

        //生成临时文件
        StringBuilder sbTempFileName = new StringBuilder();
        StringBuilder sbTempFilePath = new StringBuilder();

        sbTempFileName.append(outPath);
        sbTempFileName.append(SnowFlakeGenerator.generateIdStr());
        sbTempFileName.append(".docx");
        String tempFileName = sbTempFileName.toString();
        sbTempFilePath.append(tempFileName);
        String tempLatPathOutPath = sbTempFilePath.toString();
        // 创建临时文件
        File file = new File(tempLatPathOutPath);
        try (FileOutputStream outStream = new FileOutputStream(tempLatPathOutPath)) {
            // 插入文件流
            templateDoc = new XWPFDocument(fis);
            parseTemplateWord();
            templateDoc.write(outStream);
            outStream.close();


            //创建一个缓冲输入流对象
            br = new BufferedInputStream(new FileInputStream(tempLatPathOutPath));
            byte[] buf = new byte[ONE_ZERO_TWO_FOUR];
            int len;

            rep.reset();
            rep.setContentType("application/x-msdownload");
            rep.setContentLength((int) file.length());
            rep.setHeader("Content-Disposition",
                    "attachment; filename=" + java.net.URLEncoder.encode(showName, "UTF-8"));
            //创建输出流对象
            outputStream = rep.getOutputStream();
            //开始输出
            while ((len = br.read(buf)) > 0) {
                outputStream.write(buf, 0, len);
            }
            //删除临时文件
            if (!file.delete()) {
                log.error("删除临时文件失败");
            }
        }
        catch (Exception e) {
            StackTraceElement[] stackTrace = e.getStackTrace();

            String className = stackTrace[0].getClassName();
            String methodName = stackTrace[0].getMethodName();
            int lineNumber = stackTrace[0].getLineNumber();

            log.error("错误：第:{}行, 类名:{}, 方法名:{}", lineNumber, className, methodName);
            throw new RuntimeException(e.getCause()
                    .getMessage());
        }
        finally {
            if (br != null) {
                br.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
            if (fis != null) {
                fis.close();
            }
        }

    }

    /**
     * 解析word模板
     */
    public void parseTemplateWord() throws Exception {

        List<IBodyElement> elements = templateDoc.getBodyElements();

        for (; n < elements.size(); n++) {
            IBodyElement element = elements.get(n);
            // 普通段落
            if (element instanceof XWPFParagraph) {

                XWPFParagraph paragraph = (XWPFParagraph) element;
                oldParagraph = paragraph;
                if (paragraph.getParagraphText()
                        .isEmpty()) {
                    continue;
                }

                delParagraph(paragraph);

            }
            else if (element instanceof XWPFTable) {
                // 表格
                isTable = true;
                XWPFTable table = (XWPFTable) element;

                delTable(table, paramMap);
                isTable = false;
            }
        }

    }

    /**
     * 设置参数
     *
     * @param paramMap paramMap
     */
    public void setParamMap(Map<String, Object> paramMap) {
        this.paramMap = paramMap;
    }

    /**
     * 创建段落 <p></p>
     *
     * @param texts
     */
    public XWPFParagraph createParagraph(String... texts) {

        // 使用游标创建一个新行
        XmlCursor cursor = oldParagraph.getCTP()
                .newCursor();
        XWPFParagraph newPar = templateDoc.insertNewParagraph(cursor);
        // 设置段落样式
        newPar.getCTP()
                .setPPr(oldParagraph.getCTP()
                        .getPPr());

        copyParagraph(oldParagraph, newPar, texts);

        return newPar;
    }

    /**
     * 处理表格（遍历）
     *
     * @param table    表格
     * @param paramMap 需要替换的信息集合
     */
    public void delTable(XWPFTable table, Map<String, Object> paramMap) throws Exception {
        List<XWPFTableRow> rows = table.getRows();
        for (int i = 0, size = rows.size(); i < size; i++) {
            XWPFTableRow row = rows.get(i);
            currentRowIndex = i;
            // 如果是动态添加行 直接处理后返回
            if (delAndJudgeRow(table, paramMap, row)) {
                return;
            }
        }
    }

    /**
     * 处理段落
     *
     * @param paragraph paragraph
     * @throws Exception Exception
     */
    private void delParagraph(XWPFParagraph paragraph) throws Exception {
        List<XWPFRun> runs = oldParagraph.getRuns();
        StringBuilder sb = new StringBuilder();
        for (XWPFRun run : runs) {
            String text = run.getText(0);
            if (text == null) {
                continue;
            }
            sb.append(text);
            run.setText("", 0);
        }
        setPlaceholder(paragraph, runs, sb);
    }

    /**
     * 处理占位符
     *
     * @param runs 当前段的runs
     * @param sb   当前段的内容
     * @throws Exception Exception
     */
    private void setPlaceholder(XWPFParagraph currentPar, List<XWPFRun> runs, StringBuilder sb) throws Exception {
        if (runs.size() > 0) {
            String beforeDeal = sb.toString();
            String text = beforeDeal.replace("\n", "");
            XWPFRun currRun = runs.get(0);
            if (PoiWordUtils.isPicture(text)) {
                // 该段落是图片占位符
                ImageEntity imageEntity = (ImageEntity) PoiWordUtils.getValueByPlaceholder(paramMap, text);
                int indentationFirstLine = currentPar.getIndentationFirstLine();
                // 清除段落的格式，否则图片的缩进有问题
                currentPar.getCTP()
                        .setPPr(null);
                //设置缩进
                currentPar.setIndentationFirstLine(indentationFirstLine);
                addPicture(currRun, imageEntity);
            }
            else {
                changeValue(currRun, text, paramMap);
            }
        }
    }

    /**
     * 添加图片
     *
     * @param currRun     当前run
     * @param imageEntity 图片对象
     * @throws org.apache.poi.openxml4j.exceptions.InvalidFormatException InvalidFormatException
     * @throws java.io.FileNotFoundException  FileNotFoundException
     */
    private void addPicture(
            XWPFRun currRun, ImageEntity imageEntity) throws InvalidFormatException, FileNotFoundException {
        try {
            URL url = new URL(imageEntity.getUrl());
            HttpURLConnection urlCon = (HttpURLConnection) url.openConnection();
            urlCon.setConnectTimeout(5000);
            urlCon.setReadTimeout(5000);
            InputStream fis = urlCon.getInputStream();

            Integer typeId = imageEntity.getTypeId()
                    .getTypeId();
            String picId = currRun.getDocument()
                    .addPictureData(fis, typeId);
            ImageUtils.createPicture(currRun, picId, templateDoc.getNextPicNameNumber(typeId), imageEntity.getWidth(),
                    imageEntity.getHeight());
        }
        catch (Exception e) {
            log.error("添加图片异常:{}", e.getMessage());
        }

    }

    /**
     * 匹配传入信息集合与模板
     *
     * @param placeholder 模板需要替换的区域()
     * @param paramMap    传入信息集合
     *                    模板需要替换区域信息集合对应值
     */
    public void changeValue(XWPFRun currRun, String placeholder, Map<String, Object> paramMap) throws Exception {

        String placeholderValue = placeholder;
        if (paramMap == null || paramMap.isEmpty()) {
            return;
        }

        Set<Map.Entry<String, Object>> textSets = paramMap.entrySet();
        for (Map.Entry<String, Object> textSet : textSets) {
            //匹配模板与替换值 格式${key}
            String mapKey = textSet.getKey();
            String docKey = PoiWordUtils.getDocKey(mapKey);

            if (placeholderValue.indexOf(docKey) != -1) {
                Object obj = textSet.getValue();
                // 需要添加一个list
                if (obj instanceof List) {
                    placeholderValue = delDynList(placeholder, (List) obj);
                }
                else {
                    placeholderValue = placeholderValue.replaceAll(PoiWordUtils.getPlaceholderReg(mapKey),
                            String.valueOf(obj));
                }
            }
        }

        currRun.setText(placeholderValue, 0);
    }

    /**
     * 处理的动态的段落（参数为list）
     *
     * @param placeholder 段落占位符
     * @param obj         list对象
     * @return
     */
    private String delDynList(String placeholder, List obj) {
        String placeholderValue = placeholder;
        List dataList = obj;
        Collections.reverse(dataList);
        for (int i = 0, size = dataList.size(); i < size; i++) {
            Object text = dataList.get(i);
            // 占位符的那行, 不用重新创建新的行
            if (i == 0) {
                placeholderValue = String.valueOf(text);
            }
            else {
                XWPFParagraph paragraph = createParagraph(String.valueOf(text));
                if (paragraph != null) {
                    oldParagraph = paragraph;
                }
                // 增加段落后doc文档会的element的size会随着增加（在当前行的上面添加），回退并解析新增的行（因为可能新增的带有占位符，这里为了支持图片和表格）
                if (!isTable) {
                    n++;
                }
            }
        }
        return placeholderValue;
    }

    /**
     * 判断并且是否是动态行，并且处理表格占位符
     *
     * @param table    表格对象
     * @param paramMap 参数map
     * @param row      当前行
     * @return boolean
     * @throws Exception Exception
     */
    private boolean delAndJudgeRow(XWPFTable table, Map<String, Object> paramMap, XWPFTableRow row) throws Exception {
        // 当前行是动态行标志
        if (PoiWordUtils.isAddRow(row)) {
            List<XWPFTableRow> xwpfTableRows = addAndGetRows(table, row, paramMap);
            // 回溯添加的行，这里是试图处理动态添加的图片
            for (XWPFTableRow tbRow : xwpfTableRows) {
                delAndJudgeRow(table, paramMap, tbRow);
            }
            return true;
        }

        // 如果是重复添加的行
        if (PoiWordUtils.isAddRowRepeat(row)) {
            List<XWPFTableRow> xwpfTableRows = addAndGetRepeatRows(table, row, paramMap);
            // 回溯添加的行，这里是试图处理动态添加的图片
            for (XWPFTableRow tbRow : xwpfTableRows) {
                delAndJudgeRow(table, paramMap, tbRow);
            }
            return true;
        }
        // 当前行非动态行标签
        List<XWPFTableCell> cells = row.getTableCells();
        for (XWPFTableCell cell : cells) {
            //判断单元格是否需要替换
            if (PoiWordUtils.checkText(cell.getText())) {
                List<XWPFParagraph> paragraphs = cell.getParagraphs();
                for (XWPFParagraph paragraph : paragraphs) {
                    List<XWPFRun> runs = paragraph.getRuns();
                    StringBuilder sb = new StringBuilder();
                    for (XWPFRun run : runs) {
                        sb.append(run.toString());
                        run.setText("", 0);
                    }
                    setPlaceholder(paragraph, runs, sb);
                }
            }
        }
        return false;
    }

    /**
     * 添加行  标签行不是新创建的
     *
     * @param table    表格
     * @param flagRow  flagRow 表有标签的行
     * @param paramMap 参数
     */
    private List<XWPFTableRow> addAndGetRows(
            XWPFTable table, XWPFTableRow flagRow, Map<String, Object> paramMap) throws Exception {
        List<XWPFTableCell> flagRowCells = flagRow.getTableCells();
        XWPFTableCell flagCell = flagRowCells.get(0);

        String text = flagCell.getText();
        List<List<String>> dataList = (List<List<String>>) PoiWordUtils.getValueByPlaceholder(paramMap, text);

        // 新添加的行
        List<XWPFTableRow> newRows = new ArrayList<>(dataList.size());
        if (dataList == null || dataList.size() <= 0) {
            return newRows;
        }

        XWPFTableRow currentRow = flagRow;
        int cellSize = flagRow.getTableCells()
                .size();
        for (int i = 0, size = dataList.size(); i < size; i++) {
            if (i != 0) {
                currentRow = table.createRow();
                // 复制样式
                if (flagRow.getCtRow() != null) {
                    currentRow.getCtRow()
                            .setTrPr(flagRow.getCtRow()
                                    .getTrPr());
                }
            }
            addRow(flagCell, currentRow, cellSize, dataList.get(i));
            newRows.add(currentRow);
        }
        return newRows;
    }

    /**
     * 添加重复多行 动态行  每一行都是新创建的
     *
     * @param table    table
     * @param flagRow  flagRow
     * @param paramMap paramMap
     * @return List
     * @throws Exception Exception
     */
    private List<XWPFTableRow> addAndGetRepeatRows(
            XWPFTable table, XWPFTableRow flagRow, Map<String, Object> paramMap) throws Exception {
        List<XWPFTableCell> flagRowCells = flagRow.getTableCells();
        XWPFTableCell flagCell = flagRowCells.get(0);
        String text = flagCell.getText();
        List<List<String>> dataList = (List<List<String>>) PoiWordUtils.getValueByPlaceholder(paramMap, text);
        String tbRepeatMatrix = PoiWordUtils.getTbRepeatMatrix(text);
        //        Assert.assertNotNull("模板矩阵不能为空", tbRepeatMatrix);

        // 新添加的行
        List<XWPFTableRow> newRows = new ArrayList<>(dataList.size());
        if (dataList == null || dataList.size() <= 0) {
            return newRows;
        }

        String[] split = tbRepeatMatrix.split(PoiWordUtils.tbRepeatMatrixSeparator);
        int startRow = Integer.parseInt(split[0]);
        int endRow = Integer.parseInt(split[1]);
        int startCell = Integer.parseInt(split[2]);
        int endCell = Integer.parseInt(split[3]);

        XWPFTableRow currentRow;
        for (int i = 0, size = dataList.size(); i < size; i++) {
            int flagRowIndex = i % (endRow - startRow + 1);
            XWPFTableRow repeatFlagRow = table.getRow(flagRowIndex);
            // 清除占位符那行
            if (i == 0) {
                table.removeRow(currentRowIndex);
            }
            currentRow = table.createRow();
            // 复制样式
            if (repeatFlagRow.getCtRow() != null) {
                currentRow.getCtRow()
                        .setTrPr(repeatFlagRow.getCtRow()
                                .getTrPr());
            }
            addRowRepeat(startCell, endCell, currentRow, repeatFlagRow, dataList.get(i));
            newRows.add(currentRow);
        }
        return newRows;
    }

    /**
     * 根据模板cell添加新行
     *
     * @param flagCell    模板列(标记占位符的那个cell)
     * @param row         新增的行
     * @param cellSize    每行的列数量（用来补列补足的情况）
     * @param rowDataList 每行的数据
     */
    private void addRow(XWPFTableCell flagCell, XWPFTableRow row, int cellSize, List<String> rowDataList) {
        for (int i = 0; i < cellSize; i++) {
            XWPFTableCell cell = row.getCell(i);
            cell = cell == null ? row.createCell() : row.getCell(i);
            if (i < rowDataList.size()) {
                PoiWordUtils.copyCellAndSetValue(flagCell, cell, rowDataList.get(i));
            }
            else {
                // 数据不满整行时，添加空列
                PoiWordUtils.copyCellAndSetValue(flagCell, cell, "");
            }
        }
    }

    /**
     * 根据模板cell  添加重复行
     *
     * @param startCell     模板列的开始位置
     * @param endCell       模板列的结束位置
     * @param currentRow    创建的新行
     * @param repeatFlagRow 模板列所在的行
     * @param rowDataList   每行的数据
     */
    private void addRowRepeat(
            int startCell, int endCell, XWPFTableRow currentRow, XWPFTableRow repeatFlagRow, List<String> rowDataList) {
        int cellSize = repeatFlagRow.getTableCells()
                .size();
        for (int i = 0; i < cellSize; i++) {
            XWPFTableCell cell = currentRow.getCell(i);
            cell = cell == null ? currentRow.createCell() : currentRow.getCell(i);
            int flagCellIndex = i % (endCell - startCell + 1);
            XWPFTableCell repeatFlagCell = repeatFlagRow.getCell(flagCellIndex);
            if (i < rowDataList.size()) {
                PoiWordUtils.copyCellAndSetValue(repeatFlagCell, cell, rowDataList.get(i));
            }
            else {
                // 数据不满整行时，添加空列
                PoiWordUtils.copyCellAndSetValue(repeatFlagCell, cell, "");
            }
        }
    }

    /**
     * 复制段落
     *
     * @param sourcePar 原段落
     * @param targetPar targetPar
     * @param texts     texts
     */
    private void copyParagraph(XWPFParagraph sourcePar, XWPFParagraph targetPar, String... texts) {

        targetPar.setAlignment(sourcePar.getAlignment());
        targetPar.setVerticalAlignment(sourcePar.getVerticalAlignment());

        // 设置布局
        targetPar.setAlignment(sourcePar.getAlignment());
        targetPar.setVerticalAlignment(sourcePar.getVerticalAlignment());

        if (texts != null && texts.length > 0) {
            String[] arr = texts;
            XWPFRun xwpfRun = sourcePar.getRuns()
                    .size() > 0 ? sourcePar.getRuns()
                    .get(0) : null;

            for (int i = 0, len = texts.length; i < len; i++) {
                String text = arr[i];
                XWPFRun run = targetPar.createRun();

                run.setText(text);

                run.setFontFamily(xwpfRun.getFontFamily());
                int fontSize = xwpfRun.getFontSize();
                run.setFontSize((fontSize == -1) ? DEFAULT_FONT_SIZE : fontSize);
                run.setBold(xwpfRun.isBold());
                run.setItalic(xwpfRun.isItalic());
            }
        }
    }
}